package org.xx.armory.db;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
 * 将多行记录映射到一个实体对象的映射器。
 *
 * @param <T>
 *         实体对象的类型。
 * @author Haart
 */
public abstract class ManyToOneMapper<K, E, T>
        implements Mapper<T> {
    private K oldKey;
    private Object[] oldOthers;
    private List<E> oldChildren;

    private K currentKey;
    private Object[] currentOthers;
    private List<E> currentChildren;

    /**
     * 构造将多行记录映射到一个实体对象的映射器。
     */
    public ManyToOneMapper() {
        this.oldKey = null;
        this.currentKey = null;
        this.oldOthers = null;
        this.currentOthers = null;
        this.oldChildren = null;
        this.currentChildren = null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final boolean canGetNewResult(
            DbRow row
    ) {
        final K key = row.hasData() ? getKey(row) : null;

        final boolean result;
        if (Objects.equals(key, this.currentKey)) {
            result = false;
        } else {
            this.oldKey = this.currentKey;
            this.currentKey = key;

            final Object[] others;
            if (key != null) {
                others = getOthers(row);
                if (others == null) {
                    throw new IllegalStateException("others cannot be null!");
                }
            } else {
                others = null;
            }

            this.oldOthers = this.currentOthers;
            this.currentOthers = others;

            this.oldChildren = this.currentChildren;
            this.currentChildren = new ArrayList<>();

            result = this.oldKey != null;
        }

        if (key != null) {
            final E child = getChild(row);
            if (child != null) {
                this.currentChildren.add(child);
            }
        }

        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final T execute(
            DbRow row
    ) {
        if (this.oldKey != null) {
            final T result = createResult(this.oldKey, this.oldOthers, this.oldChildren);
            this.oldKey = null;
            this.oldOthers = null;
            this.oldChildren = null;
            return result;
        } else {
            return null;
        }
    }

    protected abstract T createResult(
            K key,
            Object[] other,
            Collection<E> children
    );

    /**
     * 获取指定数据库记录的键。
     * <p>如果两个记录的映射实体对象相同，那么这2条记录对应了同一个实体对象。</p>
     *
     * @param row
     *         数据库中的一条记录。
     * @return 该记录的键。
     */
    protected abstract K getKey(
            DbRow row
    );

    protected abstract Object[] getOthers(
            DbRow row
    );

    protected abstract E getChild(
            DbRow row
    );
}
